﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace TSDotNet.Tests
{
    static class TestUtils
    {
        public const double EQUALITY_RELATIVE_THRESHOLD = 1e-9;

        static TestUtils()
        {
            Random = new Random();
        }

        private static Random Random { get; set; }

        /// <summary>
        /// Returns a power-law distributed (fat tail) random value with given exponent and average.
        /// </summary>
        /// <remarks>
        /// Power-laws have a well defined mean only if the exponent exceeds 2 and have a finite variance only when the exponent exceeds 3.
        /// With exponent = 3 the 'natural' average is 1.
        /// See: http://en.wikipedia.org/wiki/Power_law
        /// For a derivation, see InversePowerLawDist.nb.
        /// </remarks>
        public static double PowerLawInverseDist(double average = 1.0, double exponent = 3.0)
        {
            if(exponent <= 2.0)
                throw new ArgumentOutOfRangeException("exponent must be strictly > 2 to allow finite mean.");
            var uniRand = Random.NextDouble();
            var unNormalised = Math.Pow((1.0 - uniRand), 1.0/(1.0 - exponent)) - 1.0;
            var naturalAverage = 1.0/(exponent - 2.0);
            return unNormalised/naturalAverage*average;
        }

        /// <summary>
        /// Returns an integer power-law distributed (fat tail) random value with given exponent and average.
        /// </summary>
        public static int PowerLawInverseDistInt(double average = 1.0, double exponent = 3.0)
        {
            if (exponent <= 2.0)
                throw new ArgumentOutOfRangeException("exponent must be strictly > 2 to allow finite mean.");
            return ((int) Math.Round(PowerLawInverseDist(average, exponent)));
        }

        public static double[] CsvToValues(this string csv, string separator = ",")
        {
            if (csv == null)
                return null;
            if(csv.Trim().Length==0)
                return new double[0];
            return csv
                .Split(new [] {separator}, StringSplitOptions.None)
                .Select(s => s.Trim())
                .Select(s =>
                            {
                                if (s.Length == 0) return 0.0;
                                return double.Parse(s);
                            })
                .ToArray();
        }

        public static string ToCsv<TValue>(this IEnumerable<TValue> values, string separator = ", ") 
        {
            if (values == null)
                return null;
            var list = values.ToList();
            if (list.Count == 0)
                return string.Empty;
            return list
                // No 'E' notation! -----v
                .Select(v => (((double) (object)v)).ToString("R") + separator)
                .Aggregate((s1, s2) => s1 + s2)
                .Trim(separator.ToCharArray());
        }

        public static bool AreFloatingPointEqual<TValue>(IList<TValue> v1, IList<TValue> v2)
        {
            if (v1 == null && v2 == null)
                return true;
            if (v1 == null || v2 == null)
                return false;
            if (v1.Count == 0 && v2.Count == 0)
                return true;
            if (v1.Count != v2.Count)
                return false;
            for (int i = 0; i < v1.Count; i++)
            {
                var d1 = (double) (object) v1[i];
                var d2 = (double) (object) v2[i];
                if (Math.Abs(d2 - d1)/Math.Abs(Math.Max(d1, d2)) > EQUALITY_RELATIVE_THRESHOLD)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// This actually generates a zero-mean array. Each set of positive and negative values has given absolute mean.
        /// </summary>
        public static double[] GenerateRandomArray(int averageLength, double averageValue)
        {
            int n = PowerLawInverseDistInt(averageLength);
            double[] result = new double[n];
            for (int i = 0; i < n; i++)
            {
                var value =  PowerLawInverseDist(averageValue);
                if (Random.NextDouble() < 0.25)
                    // Truncate
                    value = (int) value;
                if (Random.NextDouble() < 0.5)
                    value = -value;
                result[i] = value;
            }
            return result;
        }

    }
}
